﻿using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.DC;
using DevExpress.ExpressApp.Editors;
using DevExpress.Persistent.Validation;
using Newtonsoft.Json;
using RulesEngine.Models;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace EasyXaf.LowCode.RulesEngineEditors.Models;

[DomainComponent]
[XafDisplayName("规则")]
public class RuleObject : ModelObject, IWorkflowObjectLink, IModelObject
{
    private string _ruleName;
    private string _ruleDescription;
    private RuleOperator _operator;
    private NestedRuleOutputMode _nestedRuleOutputMode;
    private string _errorMessage;
    private bool _enabled;
    private string _expression;
    private string _successEvent;
    private bool _isCollapse;

    [RuleRequiredField]
    [XafDisplayName("名称")]
    public string RuleName
    {
        get => _ruleName;
        set => SetPropertyValue(ref _ruleName, value);
    }

    [RuleRequiredField]
    [XafDisplayName("描述")]
    public string RuleDescription
    {
        get => _ruleDescription;
        set => SetPropertyValue(ref _ruleDescription, value);
    }

    [XafDisplayName("表达式")]
    public string Expression
    {
        get => _expression;
        set => SetPropertyValue(ref _expression, value);
    }

    [Appearance("当不包含子规则时隐藏操作符", Criteria = "[Rules].Count()<=1", Visibility = ViewItemVisibility.Hide)]
    [XafDisplayName("操作符")]
    public RuleOperator Operator
    {
        get => _operator;
        set => SetPropertyValue(ref _operator, value);
    }

    [Appearance("操作符为And时隐藏输出方式", Criteria = "[Rules].Count()<=1 Or [Operator]==0", Visibility = ViewItemVisibility.Hide)]
    [XafDisplayName("嵌套规则输出方式")]
    public NestedRuleOutputMode NestedRuleOutputMode
    {
        get => _nestedRuleOutputMode;
        set => SetPropertyValue(ref _nestedRuleOutputMode, value);
    }

    [XafDisplayName("成功事件")]
    public string SuccessEvent
    {
        get => _successEvent;
        set => SetPropertyValue(ref _successEvent, value);
    }

    [XafDisplayName("错误消息")]
    public string ErrorMessage
    {
        get => _errorMessage;
        set => SetPropertyValue(ref _errorMessage, value);
    }

    [XafDisplayName("启用")]
    public bool Enabled
    {
        get => _enabled;
        set => SetPropertyValue(ref _enabled, value);
    }

    [Browsable(false)]
    public bool IsFirstRule
    {
        get
        {
            var rules = Parent?.Rules ?? Workflow.Rules;
            return rules.FirstOrDefault() == this;
        }
    }

    [Browsable(false)]
    public bool IsLastRule
    {
        get
        {
            var rules = Parent?.Rules ?? Workflow.Rules;
            return rules.LastOrDefault() == this;
        }
    }

    [Browsable(false)]
    public bool IsCollapse
    {
        get => _isCollapse;
        set => SetPropertyValue(ref _isCollapse, value);
    }

    [Browsable(false)]
    public int Level
    {
        get
        {
            var level = 0;

            var parent = Parent;
            while (parent != null)
            {
                level++;
                parent = parent.Parent;
            }

            return level;
        }
    }

    [Browsable(false)]
    public RuleObject Parent { get; set; }

    [Browsable(false)]
    public WorkflowObject Workflow { get; set; }

    [XafDisplayName("本地参数")]
    public ObservableCollection<ScopedParamObject> LocalParams { get; } = new();

    [XafDisplayName("规则动作")]
    public ObservableCollection<ActionObject> Actions { get; } = new();

    [Browsable(false)]
    public ObservableCollection<RuleObject> Rules { get; } = new();

    [Browsable(false)]
    public IEnumerable<RuleObject> AllRules
    {
        get
        {
            var rules = new List<RuleObject>(Rules);
            foreach (var rule in Rules)
            {
                rules.AddRange(rule.AllRules);
            }
            return rules;
        }
    }

    public RuleObject()
    {
        RuleName = Guid.NewGuid().ToString();
        RuleDescription = "新建的规则";
        Enabled = true;
    }

    public override IEnumerable<ValidationResult> Validate()
    {
        var validationResults = new List<ValidationResult>(base.Validate());

        if (string.IsNullOrWhiteSpace(RuleName))
        {
            validationResults.Add(new ValidationResult("规则名称不能为空", ValidationResultLevel.Error));
        }
        else if ((Parent?.Rules ?? Workflow.Rules).Count(r => r.RuleName == RuleName) > 1)
        {
            validationResults.Add(new ValidationResult("在相同父级下，规则名称不能相同", ValidationResultLevel.Error));
        }

        if (string.IsNullOrWhiteSpace(RuleDescription))
        {
            validationResults.Add(new ValidationResult("规则描述不能为空", ValidationResultLevel.Error));
        }

        foreach (var localParam in LocalParams)
        {
            foreach (var validationResult in localParam.Validate())
            {
                validationResults.Add(new ValidationResult(validationResult.Message, validationResult.Level));
            }
        }

        foreach (var action in Actions)
        {
            foreach (var validationResult in action.Validate())
            {
                validationResults.Add(new ValidationResult(validationResult.Message, validationResult.Level));
            }
        }

        foreach (var rule in Rules)
        {
            rule.Validate();
        }

        if (Actions.Count > 2)
        {
            validationResults.Add(new ValidationResult($"规则动作中最多只能包含两个动作", ValidationResultLevel.Error));
        }
        else if (Actions.Count == 2 && Actions[0].ActionType == Actions[1].ActionType)
        {
            var actionType = Actions[0].ActionType == ActionType.Success ? "成功" : "失败";
            validationResults.Add(new ValidationResult($"规则动作中只能包含一个{actionType}动作", ValidationResultLevel.Error));
        }

        ValidationResults = validationResults;
        OnValidationResultsChanged();

        return validationResults;
    }

    private static ActionObject GetActionObject(ActionInfo actionInfo, WorkflowObject workflow)
    {
        if (actionInfo?.Name == "OutputExpression")
        {
            return OutputExpressionActionObject.FromActionInfo(actionInfo, workflow);
        }
        else if (actionInfo?.Name == "EvaluateRule")
        {
            return EvaluateRuleActionObject.FromActionInfo(actionInfo);
        }
        return null;
    }

    public RuleObject Clone()
    {
        return FromRule(ToRule(), null, Workflow);
    }

    public Rule ToRule()
    {
        var rule = new Rule
        {
            RuleName = RuleName,
            Operator = Operator == RuleOperator.And ? "And" : "Or",
            ErrorMessage = ErrorMessage,
            Enabled = Enabled,
            SuccessEvent = SuccessEvent,
            RuleExpressionType = RuleExpressionType.LambdaExpression,
            Expression = !string.IsNullOrWhiteSpace(Expression) ? Expression : "true",
            Properties = new Dictionary<string, object>(),
            LocalParams = new List<ScopedParam>(),
            Actions = new RuleActions(),
            Rules = new List<Rule>(),
        };

        foreach (var parameter in LocalParams)
        {
            ((List<ScopedParam>)rule.LocalParams).Add(new ScopedParam
            {
                Name = parameter.Name,
                Expression = parameter.Expression,
            });
        }

        foreach (var actionObject in Actions)
        {
            if (actionObject.ActionType == ActionType.Success)
            {
                rule.Actions.OnSuccess = actionObject.ToActionInfo();
            }
            else
            {
                rule.Actions.OnFailure = actionObject.ToActionInfo();
            }
        }

        foreach (var ruleObject in Rules)
        {
            ((List<Rule>)rule.Rules).Add(ruleObject.ToRule());
        }

        if (rule.Rules.Any())
        {
            rule.Properties["collapse"] = IsCollapse;
        }
        else
        {
            rule.Operator = null;
        }

        if (Operator == RuleOperator.Or)
        {
            rule.Properties["nestedRuleOutputMode"] = NestedRuleOutputMode switch
            {
                NestedRuleOutputMode.One => "one",
                _ => "all"
            };
        }

        rule.Properties["ruleDescription"] = RuleDescription;
        rule.Properties["localParams"] = LocalParams
            .Select(p => new { p.Name, p.Description })
            .ToArray();

        return rule;
    }

    public static RuleObject FromRule(Rule rule, RuleObject parent, WorkflowObject workflowObject)
    {
        var ruleObject = new RuleObject
        {
            RuleName = rule.RuleName,
            ErrorMessage = rule.ErrorMessage,
            Enabled = rule.Enabled,
            Expression = rule.Expression == "true" ? string.Empty : rule.Expression,
            SuccessEvent = rule.SuccessEvent,
            Parent = parent,
            Workflow = workflowObject,
            Operator = rule.Operator switch
            {
                "And" => RuleOperator.And,
                "AndAlso" => RuleOperator.And,
                "Or" => RuleOperator.Or,
                "OrElse" => RuleOperator.Or,
                _ => RuleOperator.And
            }
        };

        if (rule.LocalParams != null)
        {
            foreach (var parameter in rule.LocalParams)
            {
                ruleObject.LocalParams.Add(new ScopedParamObject
                {
                    Name = parameter.Name,
                    Expression = parameter.Expression,
                });
            }
        }

        if (rule.Actions != null)
        {
            if (rule.Actions.OnSuccess != null)
            {
                var actionObject = GetActionObject(rule.Actions.OnSuccess, workflowObject);
                if (actionObject != null)
                {
                    actionObject.ActionType = ActionType.Success;
                    ruleObject.Actions.Add(actionObject);
                }
            }
            if (rule.Actions.OnFailure != null)
            {
                var actionObject = GetActionObject(rule.Actions.OnFailure, workflowObject);
                if (actionObject != null)
                {
                    actionObject.ActionType = ActionType.Failure;
                    ruleObject.Actions.Add(actionObject);
                }
            }
        }

        if (rule.Rules != null)
        {
            foreach (var childRule in rule.Rules)
            {
                ruleObject.Rules.Add(FromRule(childRule, ruleObject, ruleObject.Workflow));
            }
        }

        if (rule.Properties != null)
        {
            if (rule.Properties.TryGetValue("nestedRuleOutputMode", out object nestedRuleOutputModeValue))
            {
                if (nestedRuleOutputModeValue as string == "one")
                {
                    ruleObject.NestedRuleOutputMode = NestedRuleOutputMode.One;
                }
                else
                {
                    ruleObject.NestedRuleOutputMode = NestedRuleOutputMode.All;
                }
            }

            if (rule.Properties.TryGetValue("collapse", out object collapseValue))
            {
                if (bool.TryParse(collapseValue.ToString(), out var collapse))
                {
                    ruleObject.IsCollapse = collapse;
                }
            }

            if (rule.Properties.TryGetValue("ruleDescription", out object descriptionValue))
            {
                ruleObject.RuleDescription = descriptionValue as string;
            }

            if (rule.Properties.TryGetValue("localParams", out object parameterValue))
            {
                var json = JsonConvert.SerializeObject(parameterValue);
                var localParamObjects = JsonConvert.DeserializeObject<List<ScopedParamObject>>(json);
                if (localParamObjects != null)
                {
                    foreach (var localParamObject in localParamObjects)
                    {
                        var localParam = ruleObject.LocalParams.FirstOrDefault(p => p.Name == localParamObject.Name);
                        if (localParam != null)
                        {
                            localParam.Description = localParamObject.Description;
                        }
                    }
                }
            }
        }

        return ruleObject;
    }
}
